#!/bin/sh
# SPDX-License-Identifier: GPL-2.0-or-later
#
# Copyright (c) 2009 Greg Kroah-Hartman <greg@kroah.com>
# Copyright (c) 2009 Randy Dunlap <rdunlap@xenotime.net>
# Copyright (c) 2009 Frans Pop <elendil@planet.nl>
#
# When checking with shellcheck ignore
# "In POSIX sh, local is undefined."
# every modern shell supports it.
# shellcheck disable=SC3043

print_string() {
	file=$1
	name=$2
	if [ -f "$file" ]; then
		echo "S:  $name=$(cat "$file")"
	fi
}

class_decode() {
	class=$1		# v4: in hex

	case $class in
	"00") echo ">ifc " ;;
	"01") echo "audio" ;;
	"02") echo "commc" ;;
	"03") echo "HID  " ;;
	"05") echo "PID  " ;;
	"06") echo "still" ;;
	"07") echo "print" ;;
	"08") echo "stor." ;;
	"09") echo "hub  " ;;
	"0a") echo "data " ;;
	"0b") echo "scard" ;;
	"0d") echo "c-sec" ;;
	"0e") echo "video" ;;
	"0f") echo "perhc" ;;
	"10") echo "av   " ;;
	"11") echo "blbrd" ;;
	"12") echo "bridg" ;;
	"dc") echo "diagd" ;;
	"e0") echo "wlcon" ;;
	"ef") echo "misc " ;;
	"fe") echo "app. " ;;
	"ff") echo "vend." ;;
	"*")  echo "unk. " ;;
	esac
}

print_endpoint() {
	eppath=$1

	read -r addr < "$eppath/bEndpointAddress"
	read -r attr < "$eppath/bmAttributes"
	read -r dir < "$eppath/direction"

	if [ "$dir" = "in" ]; then
		dir="I"
	elif [ "$dir" = "out" ]; then
		dir="O"
	elif [ "$dir" = "both" ]; then
		dir="B"
	fi

	read -r eptype < "$eppath/type"
	if [ "$eptype" = Control ]; then
		eptype="Ctrl"
	elif [ "$eptype" = Interrupt ]; then
		eptype="Int."
	fi

	read -r maxps_hex < "$eppath/wMaxPacketSize"
	maxps_hex="0x$maxps_hex"
	# Extract MaxPS size (bits 0-10) and multiplicity values (bits 11-12)

	maxps=$(($(printf "%4i*%s\n" $((maxps_hex & 0x7ff)) \
                $((1 + ($((maxps_hex >> 11)) & 0x3))))))

	read -r interval < "$eppath/interval"

	printf "E:  Ad=%s(%s) Atr=%s(%s) MxPS=%4s Ivl=%s\n" \
		"$addr" "$dir" "$attr" "$eptype" "$maxps" "$interval"
}

print_interface() {
	ifpath=$1

	read -r ifnum < "$ifpath/bInterfaceNumber"
	read -r altset < "$ifpath/bAlternateSetting"
	read -r numeps < "$ifpath/bNumEndpoints"
	read -r class < "$ifpath/bInterfaceClass"
	read -r subclass < "$ifpath/bInterfaceSubClass"
	read -r protocol < "$ifpath/bInterfaceProtocol"

	if [ -L "$ifpath/driver" ]; then		# v4: allow for no driver
		driver=$(readlink "$ifpath/driver")
		driver=${driver##*/}
	else
		driver="(none)"
	fi
	classname=$(class_decode "$class")
	printf "I:  If#=%2i Alt=%2i #EPs=%2i Cls=%s(%s) Sub=%s Prot=%s Driver=%s\n" \
		"0x${ifnum#0}" "${altset#0}" "0x${numeps#0}" "$class" "$classname" "$subclass" \
		"$protocol" "$driver"

	for endpoint in "$ifpath"/ep_??
	do
		if [ -L "$endpoint" ] || [ -d "$endpoint" ]; then	# v4: verify endpoint exists
			print_endpoint "$endpoint"
		fi
	done
}

print_device() {
	local devpath=$1
	local parent=$2
	local level=$3
	local count=$4

	[ -d "$devpath" ] || return
	cd "$devpath" || return

	read -r busnum < "busnum"
	read -r devnum < "devnum"

	if [ "$level" -gt 0 ]; then
		port=$((${devpath##*[-.]} - 1))
	else
		port=0
	fi

	read -r speed < "speed"
	read -r maxchild < "maxchild"
	printf "\nT:  Bus=%02i Lev=%02i Prnt=%02i Port=%02i Cnt=%02i Dev#=%3i Spd=%-4s MxCh=%2i\n" \
		"$busnum" "$level" "$parent" "$port" "$count" "$devnum" "$speed" "$maxchild"

	read -r ver < "version"
	read -r devclass < "bDeviceClass"
	read -r devsubclass < "bDeviceSubClass"
	read -r devprotocol < "bDeviceProtocol"
	read -r maxps0 < "bMaxPacketSize0"
	read -r numconfigs < "bNumConfigurations"
	classname="$(class_decode "$devclass")"
	printf "D:  Ver=%5s Cls=%s(%s) Sub=%s Prot=%s MxPS=%2i #Cfgs=%3i\n" \
		"$ver" "$devclass" "$classname" "$devsubclass" "$devprotocol" \
		"$maxps0" "$numconfigs"

	read -r vendid < "idVendor"
	read -r prodid < "idProduct"
	read -r bcddevice < "bcdDevice"
	revmajor="${bcddevice%%??}"
	revminor="${bcddevice##??}"
	printf "P:  Vendor=%s ProdID=%s Rev=%s.%s\n" \
		"$vendid" "$prodid" "$revmajor" "$revminor"

	print_string manufacturer "Manufacturer"
	print_string product Product
	print_string serial SerialNumber

	read -r numifs < "bNumInterfaces"
	read -r cfgnum < "bConfigurationValue"
	read -r attr < "bmAttributes"
	read -r maxpower < "bMaxPower"
	printf "C:  #Ifs=%2i Cfg#=%2i Atr=%s MxPwr=%s\n" \
		"$numifs" "$cfgnum" "$attr" "$maxpower"

	# There's not really any useful info in endpoint 00
	#print_endpoint $devpath/ep_00

	for interface in "$busnum"-*:?.*
	do
		print_interface "$devpath/$interface"
	done

	devcount=0
	for subdev in "$busnum"-*
	do
		echo "$subdev" | grep -Eq "^$busnum-[0-9]+(\.[0-9]+)*$" \
			|| continue

		devcount=$((devcount + 1))
		if [ -d "$devpath/$subdev" ]; then
			print_device "$devpath/$subdev" \
				"$devnum" "$((level +1))" "$devcount"
		fi
	done
}

if [ ! -d /sys/bus ]; then
	echo "Error: directory /sys/bus does not exist; is sysfs mounted?" >&2
	exit 1
fi

for device in $(find /sys/bus/usb/devices -name 'usb*' | sed -E 's#^.*/##g' | sort -V)
do
	print_device "/sys/bus/usb/devices/$device" 0 0 0
done
